#pragma once

#include <memory>
#include <utility>

#include <jsi/jsi.h>

#include "JsiPromises.h"
#include "JsiSkData.h"
#include "JsiSkHostObjects.h"
#include "JsiSkImage.h"
#include "JsiSkImageInfo.h"

namespace RNSkia {

namespace jsi = facebook::jsi;

class JsiSkImageFactory : public JsiSkHostObject {
public:
  JSI_HOST_FUNCTION(MakeNull) {
    auto hostObjectInstance =
        std::make_shared<JsiSkImage>(getContext(), nullptr);
    return JSI_CREATE_HOST_OBJECT_WITH_MEMORY_PRESSURE(
        runtime, hostObjectInstance, getContext());
  }

  JSI_HOST_FUNCTION(MakeImageFromEncoded) {
    auto data = JsiSkData::fromValue(runtime, arguments[0]);
    auto image = SkImages::DeferredFromEncodedData(data);
    if (image == nullptr) {
      return jsi::Value::null();
    }
    auto hostObjectInstance =
        std::make_shared<JsiSkImage>(getContext(), std::move(image));
    return JSI_CREATE_HOST_OBJECT_WITH_MEMORY_PRESSURE(
        runtime, hostObjectInstance, getContext());
  }

  JSI_HOST_FUNCTION(MakeImageFromNativeBuffer) {
    jsi::BigInt pointer = arguments[0].asBigInt(runtime);
    const uintptr_t nativeBufferPointer = pointer.asUint64(runtime);
    void *rawPointer = reinterpret_cast<void *>(nativeBufferPointer);
    auto image = getContext()->makeImageFromNativeBuffer(rawPointer);
    if (image == nullptr) {
      throw std::runtime_error("Failed to convert NativeBuffer to SkImage!");
    }
    auto hostObjectInstance =
        std::make_shared<JsiSkImage>(getContext(), std::move(image));
    return JSI_CREATE_HOST_OBJECT_WITH_MEMORY_PRESSURE(
        runtime, hostObjectInstance, getContext());
  }

  JSI_HOST_FUNCTION(MakeImage) {
    auto imageInfo = JsiSkImageInfo::fromValue(runtime, arguments[0]);
    auto pixelData = JsiSkData::fromValue(runtime, arguments[1]);
    auto bytesPerRow = arguments[2].asNumber();
    auto image = SkImages::RasterFromData(*imageInfo, pixelData, bytesPerRow);
    if (image == nullptr) {
      return jsi::Value::null();
    }
    auto hostObjectInstance =
        std::make_shared<JsiSkImage>(getContext(), std::move(image));
    return JSI_CREATE_HOST_OBJECT_WITH_MEMORY_PRESSURE(
        runtime, hostObjectInstance, getContext());
  }

  JSI_HOST_FUNCTION(MakeImageFromViewTag) {
    auto viewTag = arguments[0].asNumber();
    auto context = getContext();
    return RNJsi::JsiPromises::createPromiseAsJSIValue(
        runtime,
        [context = std::move(context), viewTag](
            jsi::Runtime &runtime,
            std::shared_ptr<RNJsi::JsiPromises::Promise> promise) -> void {
          context->makeViewScreenshot(
              viewTag, [&runtime, context = std::move(context),
                        promise = std::move(promise)](sk_sp<SkImage> image) {
                context->runOnJavascriptThread([&runtime,
                                                context = std::move(context),
                                                promise = std::move(promise),
                                                result = std::move(image)]() {
                  if (result == nullptr) {
                    promise->reject("Failed to create image from view tag");
                    return;
                  }
                  auto hostObjectInstance =
                      std::make_shared<JsiSkImage>(context, std::move(result));
                  promise->resolve(JSI_CREATE_HOST_OBJECT_WITH_MEMORY_PRESSURE(
                      runtime, hostObjectInstance, context));
                });
              });
        });
  }

  JSI_HOST_FUNCTION(MakeImageFromNativeTextureUnstable) {
    auto texInfo = JsiTextureInfo::fromValue(runtime, arguments[0]);
    auto image = getContext()->makeImageFromNativeTexture(
        texInfo, arguments[1].asNumber(), arguments[2].asNumber(),
        count > 3 && arguments[3].asBool());
    if (image == nullptr) {
      throw std::runtime_error("Failed to convert native texture to SkImage!");
    }
    if (count > 4 && arguments[4].isObject() &&
        arguments[4].asObject(runtime).isHostObject(runtime)) {
      auto jsiImage =
          arguments[4].asObject(runtime).asHostObject<JsiSkImage>(runtime);
      jsiImage->setObject(image);
      return jsi::Value(runtime, arguments[4]);
    }
    auto hostObjectInstance =
        std::make_shared<JsiSkImage>(getContext(), std::move(image));
    return JSI_CREATE_HOST_OBJECT_WITH_MEMORY_PRESSURE(
        runtime, hostObjectInstance, getContext());
  }

  size_t getMemoryPressure() const override { return 1024; }

  std::string getObjectType() const override { return "JsiSkImageFactory"; }

  JSI_EXPORT_FUNCTIONS(JSI_EXPORT_FUNC(JsiSkImageFactory, MakeImageFromEncoded),
                       JSI_EXPORT_FUNC(JsiSkImageFactory, MakeImageFromViewTag),
                       JSI_EXPORT_FUNC(JsiSkImageFactory,
                                       MakeImageFromNativeBuffer),
                       JSI_EXPORT_FUNC(JsiSkImageFactory,
                                       MakeImageFromNativeTextureUnstable),
                       JSI_EXPORT_FUNC(JsiSkImageFactory, MakeImage),
                       JSI_EXPORT_FUNC(JsiSkImageFactory, MakeNull))

  explicit JsiSkImageFactory(std::shared_ptr<RNSkPlatformContext> context)
      : JsiSkHostObject(std::move(context)) {}
};

} // namespace RNSkia
